package com.lingjtx.common.security.limit;

import com.lingjtx.common.core.RedisOptions;
import com.lingjtx.common.core.ResponseResult;
import com.lingjtx.common.core.constant.Const;
import com.lingjtx.common.core.constant.RedisConfig;
import com.lingjtx.common.core.token.UserVo;
import com.lingjtx.common.security.annotation.ApiLimited;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.Objects;

@Component
public class ApiLimitedState extends HandlerState<ProceedingJoinPoint, HttpServletRequest> {

    private String info;

    public String getInfo() {
        return info;
    }

    public void setInfo(String info) {
        this.info = info;
    }

    @Override
    public ResponseResult<Object> retError() {
        return ResponseResult.createByError(this.getInfo());
    }

    @Override
    public void handler(ProceedingJoinPoint point, HttpServletRequest request, RedisOptions redis) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        ApiLimited annotation = signature.getMethod().getAnnotation(ApiLimited.class);
        if (Objects.nonNull(annotation)) {
            Object[] args = point.getArgs();
            Integer hashcode = null;
            if (Objects.nonNull(args)) {
                hashcode = args[0].hashCode();
            }
            // 限时行为
            if (annotation.delay() > 0) {
                String key = generateKey(request, redis, RedisConfig.LIMITED_TIME, hashcode);
                if (Objects.nonNull(redis.get(key))) {
                    super.setErrorState(true);
                    this.setInfo(String.format("请求限时，请%s分钟后尝试！", (annotation.delay() - 1) / 60 + 1));
                    return;
                }
                redis.set(key, new Date(), annotation.delay());
            }
            // 限次行为
            if (annotation.limited()) {
                if (countAlert(generateKey(request, redis, RedisConfig.LIMITED_COUNT, hashcode),
                        annotation.count(),
                        annotation.limitedExpire(),
                        redis
                )) {
                    super.setErrorState(true);
                    this.setInfo(String.format("访问次数已经使用完，请在%s小时后访问！", (annotation.limitedExpire() - 1) / 3600 + 1));
                    return;
                }
            }
        }
    }

    private String generateKey(HttpServletRequest request, RedisOptions redis, String keyPrefix, Integer hashcode) {
        String token = request.getHeader(Const.AuthTokenMap.AUTHORIZATION);
        UserVo user = (UserVo) redis.get(token);
        if (user != null) {
            return keyPrefix + request.getRequestURI() + ":" + user.getUserId();
        }
        if (Objects.nonNull(hashcode)) {
            return keyPrefix + request.getRequestURI() + ":" + hashcode;
        } else {
            return keyPrefix + request.getRequestURI() + ":" + request.getSession().getId();
        }
    }

    /**
     * 计数警告：当超过Const.RedisConfig.REQUEST_COUNT的值是警告
     *
     * @param key        key
     * @param expireTime expireTime
     * @return true-超过警告值；false-在安全值内
     */
    private boolean countAlert(String key, int count, long expireTime, RedisOptions redis) {
        int res = redis.increment(key).intValue();
        if (Objects.equals(1, res)) { // 新增
            redis.expire(key, expireTime);
        }
        return res > count;
    }
}